package com.mimo.common.configuration.mrlock.aspect;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.mimo.common.configuration.mrlock.annotation.DistributedRedisLock;
import com.mimo.common.configuration.mrlock.component.MultiRedisKeysGenerator;
import com.mimo.common.utils.ReflectionUtils;

@Aspect
public class DistributedRedisLockAspect implements Ordered {
  private static final Logger log = LoggerFactory.getLogger(DistributedRedisLockAspect.class);

  private final RedissonClient client;

  public DistributedRedisLockAspect(RedissonClient client) {
    this.client = client;

  }

  @Around(value = "@annotation(annotation)")
  public Object around(ProceedingJoinPoint joinPoint, DistributedRedisLock annotation) throws Throwable {
    Set<String> keys = null;

    if (StringUtils.hasText(annotation.key())) {
      keys = extract(annotation.key(), joinPoint);
    } else {
      keys = extract(joinPoint.getArgs(), annotation.firstEnable(),
          ReflectionUtils.newInstanceOf(annotation.generator()));
    }
    log.debug("尝试RLOCK 加锁 Key:{}", keys);
    RLock lock = null;
    if (keys.size() == 1) {
      lock = annotation.fair() ? this.client.getFairLock(keys.iterator().next())
          : this.client.getLock(keys.iterator().next());
    } else {
      RLock[] locks = keys.stream().map(k -> annotation.fair() ? this.client.getFairLock(k) : this.client.getLock(k))
          .toArray(RLock[]::new);
      lock = this.client.getMultiLock(locks);
    }
    boolean isAcquired = lock.tryLock(annotation.waited(), annotation.expired(), annotation.timeunit());
    if (isAcquired) {
      try {
        return joinPoint.proceed();
      } finally {
        lock.unlock();
        log.debug("RLOCK 解锁 Key:{}", keys);
      }
    } else {
      throw annotation.failedLockException().newInstance();
    }
  }

  /**
   * 新增用于处理Key的SPEL解析
   * 
   * @param key
   * @param joinPoint
   * @return
   */
  private Set<String> extract(String key, ProceedingJoinPoint joinPoint) {
    Set<String> ret = new HashSet<>();
    if (key.contains(ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix())) { // 如果是SPEL表达式,则需要做SPEL解析
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String[] parameters = methodSignature.getParameterNames();

      EvaluationContext context = new StandardEvaluationContext();
      for (int pIndex = 0; pIndex < parameters.length; pIndex++) {
        context.setVariable(parameters[pIndex], joinPoint.getArgs()[pIndex]);
      }
      ExpressionParser parser = new SpelExpressionParser();
      ret.add(parser.parseExpression(key, new TemplateParserContext()).getValue(context, String.class));
    } else {
      ret.add(key);
    }
    return ret;
  }

  /**
   * 主要完成从函数签名入参中，根据具体的 {@link MultiRedisKeysGenerator}找到其所需要的参数，交付到它，再由它去渲染出关键的多Key
   * 
   * @param args
   * @param generator
   * @return
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  private Set<String> extract(Object[] args, boolean firstEnable, MultiRedisKeysGenerator generator) {
    final Class clz = getClass(generator.getClass().getGenericSuperclass(), 0);

    List<Object> targets = Arrays.asList(args).stream().filter(clz::isInstance).collect(Collectors.toList());

    if (targets.isEmpty() || (targets.size() > 1 && !firstEnable)) {
      throw new IllegalArgumentException("Failed to find or found out duplicate args with same type[" + clz + "]");
    }

    Set<String> data = generator.generate(targets.get(0));
    if (CollectionUtils.isEmpty(data)) {
      throw new IllegalArgumentException("Extract Map cannot be empty or null");
    }

    return data;
  }

  @SuppressWarnings("rawtypes")
  private Class getClass(Type type, int i) {
    if (type instanceof ParameterizedType) { // 处理泛型类型
      return getGenericClass((ParameterizedType) type, i);
    } else if (type instanceof TypeVariable) {
      return getClass(((TypeVariable) type).getBounds()[0], 0); // 处理泛型擦拭对象
    } else {// class本身也是type，强制转型
      return (Class) type;
    }
  }

  @SuppressWarnings("rawtypes")
  private Class getGenericClass(ParameterizedType parameterizedType, int i) {
    Object genericClass = parameterizedType.getActualTypeArguments()[i];
    if (genericClass instanceof ParameterizedType) { // 处理多级泛型
      return (Class) ((ParameterizedType) genericClass).getRawType();
    } else if (genericClass instanceof GenericArrayType) { // 处理数组泛型
      return (Class) ((GenericArrayType) genericClass).getGenericComponentType();
    } else if (genericClass instanceof TypeVariable) { // 处理泛型擦拭对象
      return getClass(((TypeVariable) genericClass).getBounds()[0], 0);
    } else {
      return (Class) genericClass;
    }
  }

  @Override
  public int getOrder() {
    return 0;
  }

}
